import _ from 'lodash';
const { merge, mergeWith } = _;
// import { mergeWith, merge } from 'lodash-es'; //没有此库的types所以会报错
let a: string = '3'
console.log(a)
 
// 1.善用类型注释
/** person information */
 interface User{
  name: string;
  age: number;
  sex: "male" | "female";
 }
 const p:User = {
  name: "Jscs",
  age: 18,
  sex: 'male'
 }

//  2.善用类型拓展
// Point用接口interface去定义 {x:number;y:number}的类型
//  interface
interface Point{
  x: number;
  y: number;
}
interface SetPoint{
  (x:number,y:number): void;
}
// Point用type去定义 {x:number;y:number}的类型,注意有个等于= 接口interface没有等于
// type
type Point1 = {
  x: number;
  y: number;
}

type SetPoint1 = (x: number, y: number) => void;



// Point2利用扩展来变成 {x:number;y:number}的类型
// Interface extends interface 
interface PartialPointX {
  x: number;
}
interface Point2 extends PartialPointX {
  y: number
}


// Point3利用扩展来变成 {x:number;y:number}的类型
type PartialPointX1 = {
  x: number;
}
type Point3 = PartialPointX1 & {y:number}


// 接口与类型扩展并不排斥
// Point4利用type扩展来变成 {x:number;y:number}的接口
// Interface extends interface 
type PartialPointX2 = {
  x: number;
}
interface Point4 extends PartialPointX2 {
  y: number
}



// Point5利用interface扩展来变成 {x:number;y:number}的类型
interface PartialPointX3 {
  x: number;
}
type Point5 = PartialPointX3 & {y:number}

// 第三方声明文件
var object = {
  'a': [{ 'b': 2 }, { 'd': 4 }]
};

var other = {
  'a': [{ 'c': 3 }, { 'e': 5 }]
};

function customizer(objValue:Object, srcValue:Object) {
  if (Array.isArray(objValue)) {
    return objValue.concat(srcValue);
  }
}

// console.log(_.merge(object, other));
// console.log(_.mergeWith(object, other, customizer));
console.log(merge(object, other));
console.log(mergeWith(object, other, customizer));


// 只要结尾以d.ts的都会被ts搜索到

// 利用全局类型Domains
const columns: Domains.ListItem[] = []
// 利用全局类型Departments
const columns2: Departments.ListItem[] = []

// 可选链

/**
 * 支持如下
 * obj?.prop
 * obj?.[expr]
 * arr?.[index]
 * func?.(args)
 */

const user = {
  info: {
    getAge: ()=>{
      console.log(18)
    }
  }
}

let age = user?.info?.getAge?.()

// 空值合并运算符??

const user2:any = {
  level: 0
}

var level1 = user2.level ?? '暂无等级'
var level2 = user2.otherlevel ?? '暂无等级' 

console.log("level1",level1);
console.log("level2",level2);

var level1 = user2.level ?? '暂无等级'
var level2 = user2.otherlevel ?? '暂无等级' 

class Person{
  private name:string;
  private age: number;
  // static 关键字,可以将类型属性和方法定义为静态方法
  public static sex: string = "Male";
  constructor(name:string,age: number){
    this.name = name
    this.age = age
  }
  public run():void{
    console.log(this.age+this.name+"在跑步")
  }
  public setName(name: string):void{
    this.name = name
  }
  public setAge(age: number):void{
    this.age = age
  }
}

var p1:Person = new Person("Tony",22)
console.log("p1",p1);
console.log("p1.sex",Person.sex);
p1.run()
// console.log("p1.run",);
// console.log("p1.age",p1.age);
// console.log("p1.name",p1.name); 私有属性无法访问

// 类型收窄
// 1.类型断言

interface Cat{
  name: string;
  run(): void;
}
interface Fish{
  name: string;
  swim(): void;
}

function getName(animal : Cat | Fish){
  return animal.name
}

var cat:Cat = {
  name: "c1",
  run(){
    console.log(this.name+"is running");
  }
}

getName(cat)

// 不确定类型的时候就访问其中一个类型特有的属性或方法，这样写会报错 , 因为无法确定是Cat还是Fish,解决方案是使用在判断的时候断言
// function isFish(animal: Cat | Fish){
//   if(typeof animal.swim === 'function'){
//     return true
//   }
//   return false
// }
function isFish(animal: Cat | Fish){
  if(typeof (animal as Fish).swim === 'function'){
    return true
  }
  return false
}

// 2.类型守卫
/**
 * 类型守卫主要有以下几种方式：
  typeof：用于判断 number，string，boolean或 symbol 四种类型；
  instanceof：用于判断一个实例是否属于某个类
  in：用于判断一个属性/方法是否属于某个对象 
 */

type Foo = string | number;
function controlWithNever(foo: Foo){
  if(typeof foo === "string"){
    // 这里foo被收窄为string类型
    console.log("string",foo);
  }else if(typeof foo === "number"){
    // 这里foo被收窄为number类型
    console.log("number",foo);
  }else{
    // foo这里是never,表示Foo之外的类型
    const check: never = foo;
    console.log("never",foo);
  }
}
controlWithNever("1")
// 此时如果在Foo加个类型boolean,如果不加上boolean的判断 ts还是会检测出类型不对
type Foo2 = Foo | boolean
function controlWithNever2(foo: Foo2){
  if(typeof foo === "string"){
    // 这里foo被收窄为string类型
    console.log("string",foo);
  }else if(typeof foo === "number"){
    // 这里foo被收窄为number类型
    console.log("number",foo);
  }else if(typeof foo === "boolean"){
    // 这里foo被收窄为number类型
    console.log("boolean",foo);
  }else{
    // foo这里是never,表示Foo之外的类型
    const check: never = foo; 
    console.log("never",check);
  }
}
controlWithNever2(true)

// instanceof运算符收窄变量类型

class Man {
  handsome = "handsome"
}
class Woman {
  beautiful = "beautiful"
}
function Human(arg : Man |Woman){
  if(arg instanceof Man){
    console.log(arg.handsome);
    // console.log(arg.beautiful); ts会检测到类型不对
  }else {
    // 这块中一定是Woman的实例
    console.log(arg.beautiful)
  }
}
const h1:Man = new Man
Human(h1)
// in做属性检查

interface A{
  a: string
}

interface B{
  b: string
}

function fooAB(x:A|B){
  if("a" in x){
    return x.a
  }
  return x.b
}

const f1:B = {
  b: "B.b"
}
console.log(fooAB(f1));

// 3.双重断言

function handler(event: Event){
  // 此处会报错型 "Event" 到类型 "HTMLElement" 的转换可能是错误的，因为两种类型不能充分重叠。如果这是有意的，请先将表达式转换为 "unknown"。
  // const element  = event as HTMLElement
  // 正确写法
  const element  = event as any as HTMLElement

}
// 除非迫不得已，千万别用双重断言。
// 任何类型都可以被断言为 any，而 any 可以被断言为任何类型。
// 双重断言的常用情景
// ts3中新增了一种 unknown 类型，它是一种更加安全的 any 的副本。所有东西都可以被标记成是 unknown 类型，但是 unknown 必须在进行类型判断和条件控制之后才可以被分配成其他类型，并且在类型判断和条件控制之前也不能进行任何操作。

// 比如一个库 
// import {method} from 'js-lib'
// const method = (some:SomeType )=>void
// 是由于声明文件没有及时更新，它实际还接受另一种类型的参数，比如 null。如果直接传入null,那么编辑器会报错
// method(null)
// 此时可以利用unknown类型双重断言来传入null
// method(null as unknown as SomeType )
// 这样就可以通过编译器校验

// 枚举
// 枚举分为const与非const const枚举不能在枚举中改变值
const enum Direction{
  Up,
  Down,
  Left,
  // Left = '1',这样写会报错
  Right
}


let directions = [Direction.Up,Direction.Down,Direction.Left,Direction.Right]
console.log(directions);
// 常数枚举与普通枚举的区别是，前者会在编译阶段被移除，并且不能包含计算成员（即常量枚举成员初始值设定项只能包含文字值和其他计算的枚举值）。
// 非常量枚举通常用来定义起始值枚举的起始值
enum Direction2 {
  Up,
  Down,
  // Left,
  Left = 4,
  Right
}


// 高级类型
// 1类型索引
interface Button{
  type: string
  text: string
}

type ButtonKeys = keyof Button
// 等效于
// type ButtonKeys = "type" | "text"

interface Button1 {
  name: ButtonKeys
}

let btn1: Button1 = {
  name: "type"
}

// 2类型约束
// 泛型本质就是函数重载 保证参数与返回值的类型一致 然后不用重复写

type BaseType = string | number | boolean
// 这里表示copy的参数
// 只能是字符串 数字 布尔这几种基础类型
function copy <T extends BaseType>(arg:T):T{
  return arg
}

const num = copy(123)
// const arr = copy([]) //这里[]不属于BaseType了

// extends 经常与 keyof 一起使用，例如我们有一个 getValue 方法专门用来获取对象的值，但是这个对象并不确定，我们就可以使用 extends 和 keyof 进行约束：

function getValue<T,K extends keyof T>(obj:T,key:K){
  return obj[key]
}

const obj = {
  a:1
}
const a1 = getValue(obj,'a')
// const b1 = getValue(obj,'b') 此处会报错

//3类型映射
//in 遍历已有接口的 key 或者是遍历联合类型
type Readonly<T> = {
  readonly [P in  keyof T]: T[P]
}
